<template>
  <div v-m-click-outside class="calendar">
<!--    <span>{{}}</span>-->
    <input type="text" :placeholder="title" class="placeholder">
    <input type="text" :value="getDate" class="date-value">
    <div v-if="isShow" class="calendar-panel">
      <div class="calendar-panel-nav">
        <span @click="preTenYear">&lt;&lt;<</span>
        <span @click="preYear">&lt;&lt;</span>
        <span @click="preMonth">&lt;</span>
        <span>{{date.year}}年</span>
        <span>{{date.month + 1}}月</span>
        <span @click="nextMonth">&gt;</span>
        <span @click="nextYear">&gt;&gt;</span>
        <span @click="nextTenYear">>&gt;&gt;</span>
      </div>
      <div class="calendar-panel-content">
        <div class="days">

          <span class="box" v-for="i in 7" :key="`_` + i"> <!--因为下面的所以也有1、2、3、4、5、6、7,这里直接使用这个索引控制台会报错,可以使用这种方法让索引不重名-->
            {{weeks[i-1]}}
          </span>

          <!--排版思路:依次展示出6行7列的日期,6行可以使用6个div实现;7列可以使用每个div内的span实现
          样式思路:因为一个月有展示有多个月的日期,所以要判断展示的42天中是否是本月的天,如果不是变灰色;反之,加重字体,可以使用绑定class实现
          是否为当天的思路:获取当前的日期,并通过这个日期获取其日,并把这个月的42天的日逐个与其对比,绑定到class得以实现-->
          <div v-for="i in 6" :key="i">
            <span v-for="j in 7" :key="j" class="box day-box"
              :class="[
                  {notCurrentMonth: !isCurrentMonth(visibleDay[(i - 1) * 7 + (j - 1)])},
                  {today: isCurrentDay(visibleDay[(i - 1) * 7 + (j - 1)])},
                  {select: isSelect(visibleDay[(i - 1) * 7 + (j - 1)])}
                  ]"
              @click="chooseDate(visibleDay[(i - 1) * 7 + (j - 1)])">
              {{visibleDay[(i - 1) * 7 + (j - 1)].getDate()}}
            </span>
          </div>
<!--          {{visibleDay}}-->
        </div>
      </div>
      <div class="calendar-panel-footer"></div>
    </div>
  </div>
</template>

<script>
  // import {getYearMonthDayByDate, getDateByYearMonthDay} from '../../../common/utils'
  export default {
    /**考虑到要点击显示的日历选择日期,所以这里不能用传统的@focus和@blur配合methods实现;而是借助于如下的directives自定义指令实现**/
    directives: { // 用来自定义指令。(所有的自定义指令都是以v-开头,所有大写变成-小写)
      // mClickOutside() { // 考虑到指令的生命周期,使用下面的方法定义，会被触发多次(绑定和更新时都会刷新)
      // },
      mClickOutside: {
        bind(el, bindings, vNode) { // 只让其绑定触发,参数1:当前的绑定元素; 参数2: 参数3:当前虚拟DOM

          let handler = (e) => {
            // console.log(e.target);
            if (el.contains(e.target)) { // 如果当前的组件存在,即绑定此指令的组件div
              if (!vNode.context.isShow) { // 如果当前虚拟DOM的上下文DOM没有显示
                vNode.context.mFocus() // 调用当前虚拟DOM的上下文DOM的mFocus函数,以使当前虚拟DOM的上下文DOM显示
                console.log('focus');
              }
            } else {
              if (vNode.context.isShow) { // 如果当前虚拟DOM的上下文DOM显示
                vNode.context.mBlur() // 调用当前虚拟DOM的上下文DOM的mBlur函数,以使当前虚拟DOM的上下文DOM隐藏
                console.log('blur');
              }
            }
          }

          el.handler = handler //保存在当前的DOM上,以使在组件销毁时解绑移除掉
          document.addEventListener('click', handler)
        },
        unbind(el) { // 解绑
          document.removeEventListener('click', el.handler)
        }
      }
    },
    name: "Calendar",
    props: {
      pDate: { // 父组件传来的日期
        type: Date,
        // default: () => new Date() // 默认为当前日期,注意使用函数,或者使用如下
        default() {
          return new Date()
        }
      },
      title: {
        type: String,
        default: ''
      }
    },
    data() {
      let {year, month} = this.getYearMonthDayByDate(this.pDate) // 每次调用这个组件获取一次父组件传来的日期的年月日,在之后暂存如date数据,作用见date
      return {
        isShow: false, // 日历面板是否可见
        weeks: ['日', '一', '二', '三', '四', '五', '六'],
        date: { // 用来暂存年月日,因为在切换年和月时,没有选择日,即没有选择日期,所以不会改变文本框的值,所以用这两个变量把年和月暂存起来,并显示到年和月面板而非改变日期
          year,
          month,
        }
      }
    },
    computed: {
      getDate() { // 返回一个格式化的时间
        let {year, month, day} = this.getYearMonthDayByDate(new Date(this.pDate.getTime() + 8 * 60 *60 * 1000)) // 东八区+8小时
        return `${year}-${month + 1}-${day}`
      },

      /**
       * 分析日历可以发现每个月的日历显示有3个月的日期,且每个月都展示42天(6行7列<周日到周6>)
       * 再分析可以看出,当前月的第一天是周几,就把要展示的天数往前移动几天,然后再次循环42次,即就是这个月要展示的42天
       */
      visibleDay() { // 这个函数用来展示每个月的42天
        let CompFirstLoadDate = this.getDateByYearMonthDay(this.date.year, this.date.month, 18) //通过此组件首次加载时父组件传入年、月得出Date日期,注意这里的日期无所谓,因为虽然通过年月日获取Date后再得到了年、月、日,没有接收日期,所以日填什么无所谓
        let {year, month} = this.getYearMonthDayByDate(CompFirstLoadDate) // 通过首次加载此组件传入的Date确认年和月
        let currentMonthOfFirstDayOfDate = this.getDateByYearMonthDay(year, month, 1) // 获取当前月的第一天的Date,注意因为月份在获取时为了正确的展示已经做了+1操作,所以此时通过年月日获取Date需要把月份-1
        let week = currentMonthOfFirstDayOfDate.getDay(); // 获取当前月的第一天是周几
        let startDate = currentMonthOfFirstDayOfDate - 1000 * 60 * 60 * 24 * week // 通过每个月的第一天是周几来决定每个月开始循环42天的起始日期
        let visibleDays = []; // 存放这个月展示的42天
        for(let i = 0; i < 42; i++) {
          visibleDays.push(new Date(startDate + 1000 * 60 * 60 * 24 * i)) // 依次把每个月要展示的42天的日期放入数组,但是Date类型做完加减运算会变成时间戳,所以应再次使用new Date() 方法变成标准的日期格式
        }
        return visibleDays; // 把这个月要展示的日期返回
      }

    },
    methods: {
      mFocus() {
        this.isShow = true
      },
      mBlur() {
        this.isShow = false
        let placeholder = document.getElementsByClassName('placeholder')[0]
        placeholder.value = '' // 防止用户在placeholder的文本框输入值,因此清除掉它
      },
      isCurrentMonth(currentDateDay) { //判断是否为当月的日期
        let CompFirstLoadDate = this.getDateByYearMonthDay(this.date.year, this.date.month, 18) //通过此组件首次加载时父组件传入年、月得出Date日期,注意这里的日期无所谓,因为虽然通过年月日获取Date后再得到了年、月、日,没有接收日期,所以日填什么无所谓
        let {year, month} = this.getYearMonthDayByDate(CompFirstLoadDate) // 通过首次加载此组件传入的Date确认年和月
        let {year: y, month: m} = this.getYearMonthDayByDate(currentDateDay) // 通过从这个月展示的42天中的某一天获取其年月,注意因为这个函数返回的值重名了,使用这种方式实现重命名
        return year === y && month === m // 如果这个成立表示是当前年月,返回true;反之,返回false
      },
      isCurrentDay(currentDateDay) { // 判断是否为当天

        let {year, month, day} = this.getYearMonthDayByDate(new Date()) // 获取当前时间的年月日
        let {year: y, month: m, day: d} = this.getYearMonthDayByDate(currentDateDay) // 通过从这个月展示的42天中的某一天获取其年月日,注意因为这个函数返回的值重名了,使用这种方式实现重命名

        // console.log('当天:' + year + '-' + month + '-' + day);
        // console.log('传入:' + y + '-' + m + '-' + d);
        return year === y && month === m && day === d
      },
      isSelect(currentDate) {
        let {year, month, day} = this.getYearMonthDayByDate(this.pDate) // 获取用户的年月日
        let {year: y, month: m, day: d} = this.getYearMonthDayByDate(currentDate) // 通过从这个月展示的42天中的某一天获取其年月日,注意因为这个函数返回的值重名了,使用这种方式实现重命名
        return year === y && month === m && day === d
      },
      chooseDate(cDate) { // 点击选中日期
        this.date = this.getYearMonthDayByDate(cDate); // 通过获取年月日的方法把date里面的年月覆盖更新掉,以使更改完日期后再次进入年和月显示正确
        console.log('cDate=' + cDate);
        this.$emit('changeDate', cDate); // 使用事件发射方式向父组件发射事件以改变props的值
        // this.$emit('input', cDate) // 发射事件input可以直接在父组件使用v-model改变父组件的值和此props的值
        this.mBlur(); // 调用mBlur()方法隐藏日历
      },
      preYear() { // 点击上一年
        let dateDate = this.getDateByYearMonthDay(this.date.year, this.date.month, 18)
        dateDate.setFullYear(dateDate.getFullYear() - 1)
        this.date = this.getYearMonthDayByDate(dateDate)
      },
      nextYear() { // 点击下一年
        let dateDate = this.getDateByYearMonthDay(this.date.year, this.date.month, 18)
        dateDate.setFullYear(dateDate.getFullYear() + 1)
        this.date = this.getYearMonthDayByDate(dateDate)
      },
      preTenYear() { // 点击前十年
        let dateDate = this.getDateByYearMonthDay(this.date.year, this.date.month, 18)
        dateDate.setFullYear(dateDate.getFullYear() - 10)
        this.date = this.getYearMonthDayByDate(dateDate)
      },
      nextTenYear() { // 点击后十年
        let dateDate = this.getDateByYearMonthDay(this.date.year, this.date.month, 18)
        dateDate.setFullYear(dateDate.getFullYear() + 10)
        this.date = this.getYearMonthDayByDate(dateDate)
      },
      preMonth() { // 点击上一个月
        // this.date.month-- // 但是这样会出现0、-1、-2、..
        let dateDate = this.getDateByYearMonthDay(this.date.year, this.date.month, 18) // 获取date的Date类型
        dateDate.setMonth(dateDate.getMonth() - 1) // 通过这个修改月的方法不会出现上述的情况
        this.date = this.getYearMonthDayByDate(dateDate) // 把修改以后的Date类型的date变成年月日形式传入到date中,实现修改月
      },
      nextMonth() { // 点击下一个月
        // this.date.month++ // 但是这样会出现13、14、15、...
        let dateDate = this.getDateByYearMonthDay(this.date.year, this.date.month, 18) // 获取date的Date类型
        dateDate.setMonth(dateDate.getMonth() + 1) // 通过这个修改月的方法不会出现上述的情况
        this.date = this.getYearMonthDayByDate(dateDate) // 把修改以后的Date类型的date变成年月日形式传入到date中,实现修改月
      },


    /**
    * 两个工具方法
    **/
      /**获取通过Date年月日*/
      getYearMonthDayByDate(date) {
        let year = date.getFullYear()
        let month = date.getMonth() // 这里的月最好不要加1,否则在后续使用年月日进行相应的计算时会变得异常繁琐且许多加1减1问题;因为这个工具方法在上面的业务操作中会多次调用这个方法,所以,这里的最好不加1,而是在最终展示时进行加1即可
        let day = date.getDate()
        return {year, month, day}
      },
      /**通过年月日获取Date*/
      getDateByYearMonthDay(year, month, day) {
        return new Date(year, month, day)
      }
      /**
       * 两个工具方法
       **/

    }
  }
</script>

<style scoped>
  .placeholder {
    height: 32px;
    width: 60px;

    text-align: right;

    outline: none;
    border-right: none;
  }
  .date-value {
    height: 32px;
    width: 80px;

    outline: none;
    border-left: none;
  }
  .calendar-panel {
    position: absolute;
    z-index: 9;
    background-color: #fff;
    box-shadow: 2px 2px 2px rgb(60, 181, 77), -2px -2px 2px rgb(60, 181, 77);
    width: 280px; /*40*7*/
  }
  .calendar-panel-nav {
    height: 30px;
    display: flex;
    justify-content: space-around; /*两边环绕自动撑开*/
  }
  .calendar-panel-nav span {
    cursor: pointer; /*放上变手指*/
    user-select: none;/*禁止选中*/
  }
  .calendar-panel-content {

  }
  .calendar-panel-content .box {
    display: inline-flex;
    justify-content: center; /*内部居中*/
    height: 40px;
    width: 40px;
    font-weight: bold;/*默认字体加深*/
    box-sizing: border-box;/*防止加上边线影响布局*/
  }
  .calendar-panel-content .day-box {
    cursor: pointer; /*放上变手指*/
  }
  .calendar-panel-content .day-box:hover { /*鼠标放上样式效果*/
    /*border: 1px solid rgb(60, 181, 77);*/
    opacity: 0.75;
    background-color: #cdcdcd;
  }
  .notCurrentMonth { /*不是当前月*/
    color: gray; /*字体颜色变灰*/
  }
  .select { /*如果选中这个日期*/
    background-color: rgb(60, 181, 77);
    color: white;
  }
  .today { /*是当天*/
    background-color: red;
    color: white;
    border-radius: 6px;
    /*border-radius: 50% 50%;*/
  }


  .calendar-panel-footer {
    height: 30px;
    text-align: center;
  }
</style>